package primitive3D;

import static helper.Util.getVector;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import rayTracer.AABB;
import rayTracer.Ray;
import rayTracer.Shader;
import rayTracer.Transformation;


public class Quadrilateral extends Primitive3D implements Cloneable{
	private Point3d a, b, c, d;

	public Quadrilateral(Point3d a, Point3d b, Point3d c, Point3d d, Shader shader){
		this.a = new Point3d(a);
		this.b = new Point3d(b);		
		this.c = new Point3d(c);
		this.d = new Point3d(d);
		name = new String("");
		components = null;		
		this.shader = shader;
		//To calculate the position: intersection of Medians
		//Here we only find the middle point of 2 opposed sides, creates
		//a then calculate the middle point of this median
		Point3d M1 = getMiddlePoint( this.a, this.b );		
		Point3d M2 = getMiddlePoint( this.c, this.d );
		position = getMiddlePoint( M1, M2 );
		
		Point3d[] pts = new Point3d[]{a, b, c, d};
		
		double minX = Double.POSITIVE_INFINITY;
		double minY = Double.POSITIVE_INFINITY;
		double minZ = Double.POSITIVE_INFINITY;
		
		double maxX = Double.NEGATIVE_INFINITY;
		double maxY = Double.NEGATIVE_INFINITY;
		double maxZ = Double.NEGATIVE_INFINITY;
		
		for( int i=0 ; i<4 ; i++ ){
			if( pts[i].x < minX )
				minX = pts[i].x;
			if( pts[i].x > maxX )
				maxX = pts[i].x;
			if( pts[i].y < minY )
				minY = pts[i].y;
			if( pts[i].y > maxY )
				maxY = pts[i].y;
			if( pts[i].z < minZ )
				minZ = pts[i].z;
			if( pts[i].z > maxZ )
				maxZ = pts[i].z;
		}
		double[] dmin = new double[]{minX, minY, minZ};
		double[] dmax = new double[]{maxX, maxY, maxZ};
		
		bb = new AABB(new Point3d(dmin), new Point3d(dmax) );
	}
	
	public Quadrilateral(Point3d[] points, Shader shader){
		this(points[0], points[1], points[2], points[3], shader);
	}
	
	@Override
	/*
	 * Produces a pure translation
	 */
	public void setPosition(Point3d otherPosition) {
		//First move it to the origin		
		
		a.sub(this.position);
		b.sub(this.position);
		c.sub(this.position);
		d.sub(this.position);
		bb.min.sub(this.position);
		bb.max.sub(this.position);
		
		//Now move it to the new position
		a.add(otherPosition);
		b.add(otherPosition);
		c.add(otherPosition);
		d.add(otherPosition);
		bb.min.add(otherPosition);
		bb.max.add(otherPosition);
		
		this.position = new Point3d(otherPosition);			
	}
	
	/*
	 * ScaleFactor is the factor of multiplication for each side
	 * Ex: side=3 , and scaleFactor=3 => new side = 6
	 * The position of the quadrilateral does not change
	 */
	public void scale( int scaleFactor ){		
		this.scaleFromReferencePosition(position, scaleFactor);
	}
	
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor){
		this.scalePointfromReferencePosition(a, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(b, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(c, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(d, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(bb.min, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(bb.max, referencePosition, scaleFactor);
	}
	
	public String toString(){
		StringBuffer str = new StringBuffer();
		
		str.append("Quadrilateral \"" + getName() + "\"\nA = " + a + "\tB = " + b + "\tC = " + c + "\tD = " + d +"\nPosition = " + position );
		str.append("Quadrilateral \"" + getName() +"  bb.min="+bb.min+"bb.max="+bb.max);
		return str.toString();
	}
	
	@Override
	public Object clone(){
		Quadrilateral obj=null;
        try{
            obj=(Quadrilateral)super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println("No se puede duplicar");
        }        
        obj.a = (Point3d)a.clone();
        obj.b = (Point3d)b.clone();
        obj.c = (Point3d)c.clone();
        obj.d = (Point3d)d.clone();
        return obj;	    
	}
	
	//Calculates the middle point between two points p1 and p2
	private Point3d getMiddlePoint( Point3d p1, Point3d p2 ){
		double distance = p1.distance(p2)/2;
		Vector3d versor = new Vector3d(p1);			
		versor.sub(p2);
		versor.normalize();
		
		Point3d middlePoint = new Point3d();
		middlePoint.scaleAdd(distance, versor, p2);
		return middlePoint;
	}
	
	public Point3d[] getPoints() {
		return new Point3d[]{a, b, c, d};
	}
	
	public double area() {
		Triangle t1 = new Triangle(a,b,c);
		Triangle t2 = new Triangle(a,c,d);
		return t1.area() + t2.area();
	}
	
	// Devuelve la distancia al punto intersectado en el nombre de
	// la funcion. Si no hay interseccion devuelve -1.  
	public double intersects(Ray ray) {
		Triangle t = new Triangle(a,b,c);
		double distance = t.intersects(ray);
		if( distance >= 0 ) {
			return distance;
		}
		t = new Triangle(a,c,d);
		distance = t.intersects(ray);
		if( distance >= 0 ) {
			return distance;
		}
		return -1;
	}

	// Devuelve un vector normal a la superficie del cuadrilatero que pasa por p
	// p debe ser un punto de la superficie del cuadrilatero
	public Vector3d getNormal(Point3d p) {
		// Trato al cuadrilatero como un triangulo. Ambos triangulos tienen la
		// misma normal que el cuadrilatero.
		return helper.Util.getVecNormal(getVector(c, a), getVector(c, b));
	}
	
	@Override
	public int transform(Transformation trans) {
		trans.translateRotate(position);			
		trans.transformPoint(a);
		trans.transformPoint(b);
		trans.transformPoint(c);
		trans.transformPoint(d);
		
		trans.transformPoint(bb.min);
		trans.transformPoint(bb.max);
		
		return 0;
	}

}
